package plgnet

import (
	"dgo/MyProj/PrjConf"
	"dgo/MyProj/PrjHttpUtils"
	"dgo/goutils/dnet"
	"dgo/goutils/gohttp"
	"dgo/goutils/httpfs"
	"dgo/goutils/utils"
	"dgo/plg/plgbase"
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/gobase/golog"
	"gitee.com/ymofen/gobase/gotask/chantask"
	"gitee.com/ymofen/gobase/gotask/grouptask"
	"gitee.com/ymofen/supervalue"
	"golang.org/x/time/rate"
	"io"
	"net"
	"net/http"
	"runtime"
	"strings"
	"sync/atomic"
	"time"
)

var (
	DEFAULT_HTTP_RATELIMIT     = "100/10"
	DEFAULT_HTTP_RATELIMIT_PER = "2/2"
)

/*
插件Http控制插件
默认Http服务
该插件推荐只创建一个
*/
type NetHttpPlgCtrl struct {
	startedflag int32
	blockN      int32

	plgbase.PlgBase
	http_allow_cross_access bool

	webrootPath string
	caption     string
	addr        string
	startT      time.Time
	httpsvr     *gohttp.GoHttpServ
	reqlst      utils.GSyncMapGroup
	memStats    runtime.MemStats
	ratelimitN  string
	rateLimiter *rate.Limiter

	ratelimitNPer string
}

func CreateNetHttpPlgCtrlFunc(args ...interface{}) (rval interface{}, err error) {
	return &NetHttpPlgCtrl{}, nil
}

func (this *NetHttpPlgCtrl) OnCreate() {
	gohttp.CheckDefaultHttpServeMux("/")

	gohttp.DefaultHttpServeMux.OnHttpRequestEntry = func(w http.ResponseWriter, r *http.Request) bool {
		lm := this.rateLimiter
		if lm != nil && !lm.Allow() {
			http.Error(w, "访问接口限流, 请稍后进行尝试!", 503)
			atomic.AddInt32(&this.blockN, 1)
			return false
		}
		uri := r.URL.Path
		rec := this.checkGetReqRec(uri)
		return rec.OnRequest(w, r)
	}

	gohttp.StartHandleFileServ("/plug/logs/", "log/", 0, true)

	gohttp.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		data, _ := io.ReadAll(r.Body)
		if data != nil && len(data) > 0 {
			w.Write(data)
		} else {
			w.Write([]byte(fmt.Sprintf(`{"msg":"hello", "t":%d}`, time.Now().Unix())))
		}
	})

	gohttp.HandleFunc("/plug/", this.OnHttPlugService)
	gohttp.HandleFunc("/sys/cmd", this.OnHttpCmd)
	gohttp.HandleFunc("/sys/reload", this.OnHttpReload)

	gohttp.HandleFunc("/plug/warning", func(w http.ResponseWriter, r *http.Request) {
		//rec := NewPlgHttpRec(w, r)
		PrjHttpUtils.ResponseWarningList(w, plgbase.PlgWarning, "")
	})
	gohttp.HandleFunc("/plug/status", this.OnHttPlugStatus)

	gohttp.HandleFunc("/plug/close", this.OnHttpClosePlugin)

	gohttp.HandleFunc("/plug/statuslist", this.OnHttPlugStatusList)

	gohttp.HandleFunc("/plug/status/workers", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Query().Get("id")
		if len(id) == 0 {
			if r.URL.Query().Get("detail") == "1" {
				w.Write([]byte(utils.DefaultWorkers().DetailsEx2(true, 0, r.URL.Query().Get("searchval"), r.URL.Query().Get("exclusive"))))
			} else {
				w.Write([]byte(utils.DefaultWorkers().StatusString()))
			}

		} else {
			w.Write([]byte(utils.DefaultWorkers().GroupStatus(id)))
		}
	})

	gohttp.HandleFunc("/plug/status/gobase/workers", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Query().Get("id")
		if len(id) == 0 {
			if r.URL.Query().Get("detail") == "1" {
				w.Write([]byte(grouptask.DefaultGroupTask().DetailsEx2(true, 0, r.URL.Query().Get("searchval"), r.URL.Query().Get("exclusive"))))
			} else {
				w.Write([]byte(grouptask.DefaultGroupTask().StatusString()))
			}

		} else {
			w.Write([]byte(grouptask.DefaultGroupTask().GroupStatus(id)))
		}
	})

	gohttp.HandleFunc("/plug/status/gobase/chantask", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("all") == "1" {
			w.Write([]byte(chantask.DefaultChanTaskHub.StatusDetails(1)))
		} else {
			w.Write([]byte(chantask.DefaultChanTaskHub.StatusDetails(0)))
		}
	})

	gohttp.HandleFunc("/plug/status/cron", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(utils.DefaultCron().StatusString()))
	})

	gohttp.HandleFunc("/plug/do", this.OnHttPlugDo)
	gohttp.HandleFunc("/plug/changed", this.OnHttPlugChanged)
}

func (this *NetHttpPlgCtrl) Close() error {
	gohttp.UnHandle("/plug/")
	gohttp.UnHandle("/sys/cmd")
	gohttp.UnHandle("/sys/reload")
	gohttp.UnHandle("/plug/status")
	gohttp.UnHandle("/plug/statuslist")
	gohttp.UnHandle("/plug/do")
	gohttp.UnHandle("/plug/changed")
	gohttp.UnHandle("/plug/logs/")
	this.ReleaseStatReqLst()
	this.PlgBase.Close()
	this.requestDis("插件关闭")
	return nil
}

func (this *NetHttpPlgCtrl) configWebRootPath(str string) {
	if this.webrootPath != str {
		this.webrootPath = str
		if len(this.webrootPath) > 0 {
			fserv := httpfs.FileServer(http.Dir(this.webrootPath), 0, func(w http.ResponseWriter, r *http.Request) bool {
				return true
			})
			fserv.ListFiles = false
			gohttp.Handle("/", http.StripPrefix("/", fserv))
		} else {
			gohttp.UnHandle("/")
		}
	}
}

func (this *NetHttpPlgCtrl) startHttpService() {
	if gobase.GoFunCatchException {
		defer gobase.DeferCatchPanic()
	}
	if !atomic.CompareAndSwapInt32(&this.startedflag, 0, 1) {
		return
	}
	var addr string
	var addr_0 string
	for {
		if this.ReleaseFlag == 1 {
			break
		}
		if addr_0 != this.addr {
			addr_0 = this.addr
			addr = utils.ReplacePlaceholder(addr_0, "$$", "$$")
		}

		if len(addr) == 0 {
			time.Sleep(time.Second)
			continue
		}
		_, port := utils.Split2Str(addr, ":")
		utils.RegisterFactoryVar(fmt.Sprintf("%s-addr-port", this.PlugId), port)
		utils.RegisterFactoryVar("localhttp", fmt.Sprintf("http://127.0.0.1:%s", port))
		this.httpsvr = gohttp.NewGoHttpServ(addr)
		this.httpsvr.OnListenSucc = func(ls net.Listener) {
			addr1 := ls.Addr().String()
			_, port1 := utils.Split2Str(addr1, ":")
			utils.RegisterFactoryVar(fmt.Sprintf("%s-addr-port", this.PlugId), port1)
			utils.RegisterFactoryVar("localhttp", fmt.Sprintf("http://127.0.0.1:%s", port1))
			plgbase.PubNotify(this.PlugId, 1)
			golog.Infof("[%s]启动成功:%s, %s:%s", this.caption, ls.Addr().String(), fmt.Sprintf("%s-addr-port", this.PlugId), port1)
		}
		this.httpsvr.ReadTimeout = time.Second * 120 // 避免很多无用的连接不断开(ESTABLISHED)
		this.httpsvr.WriteTimeout = time.Second * 120

		golog.Infof("[%s]准备启动:%s, %s:%s", this.caption, addr, fmt.Sprintf("%s-addr-port", this.PlugId), port)
		this.startT = time.Now()

		err := this.httpsvr.ServeHttp("tcp4")
		if err != nil {
			if this.ReleaseFlag == 1 {
				golog.Infof("[%s]关闭, 停止服务:%v", this.caption, err)
				break
			}

			if time.Now().Sub(this.startT) < time.Second*10 {
				golog.Warnf("[%s]服务启动失败, 10秒后重试!:%v", this.caption, err)
				time.Sleep(time.Second * 10) // 侦听失败, 10秒后重试
			} else {
				golog.Warnf("[%s][%s]服务关闭:%v", this.caption, addr, err)
			}
		}
	}
}

func (this *NetHttpPlgCtrl) requestDis(reason string) {
	svr := this.httpsvr
	if svr != nil {
		svr.Close()
	}
}

/*
			{
			  "id": "plghttpctrl",
			  "type": "net.http.plgctrl",
			  "conf": {
			    "addr": ":6061",
			    "caption":"插件框架",
			    "webrootpath":"webroot",
		        "ratelimit":"100/2",    // 总限流
	            "ratelimit-per":"2/2"   // 每个uri限流
			  }
			}
*/
func (this *NetHttpPlgCtrl) ReloadConf(conf *wrapper.SuperValue) error {
	conf.CheckStrFunc("caption", func(strv string) {
		this.caption = strv
	})

	conf.CheckStrFunc("addr", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		if this.addr != strv {
			this.addr = strv
			this.requestDis("配置更改!")
		}
	})

	this.http_allow_cross_access = conf.BoolByName("http-allow-cross-access", true)

	str := conf.StringByName("ratelimit", DEFAULT_HTTP_RATELIMIT)
	if str != this.ratelimitN {
		s01, s02 := gobase.Split2Str(str, "/")
		n1, n2 := gobase.StrToIntDef(s01, 0), gobase.StrToIntDef(s02, 0)
		if n1 == 0 {
			this.rateLimiter = nil
		} else {
			this.rateLimiter = rate.NewLimiter(rate.Limit(n1), n2)
		}
		this.ratelimitN = str
	}

	str = conf.StringByName("ratelimit-per", DEFAULT_HTTP_RATELIMIT_PER)
	if str != this.ratelimitNPer {
		s01, s02 := gobase.Split2Str(str, "/")
		n1, n2 := gobase.StrToIntDef(s01, 0), gobase.StrToIntDef(s02, 0)
		if n1 == 0 {
			this.reqlst.Range(func(key, value interface{}) bool {
				req := value.(*PlgCtrlHttpRequestRec)
				req.RateLimiter = nil
				return true
			})
		} else {
			this.reqlst.Range(func(key, value interface{}) bool {
				req := value.(*PlgCtrlHttpRequestRec)
				req.RateLimiter = rate.NewLimiter(rate.Limit(n1), n2)
				return true
			})
		}
		this.ratelimitNPer = str
	}

	if len(this.addr) == 0 {
		plgbase.TryFlagParse(func(cmd *utils.FlagSet) {
			cmd.StringVar(&this.addr, "cmd-addr", "", "")
		})

		if len(this.addr) > 0 {
			utils.RegisterFactoryVarFunc("startup.cmd-addr", func(sender interface{}, args ...interface{}) interface{} {
				return this.addr
			})
		}
	}

	conf.CheckExistsStrFunc("webrootpath", func(strv string) {
		this.configWebRootPath(strv)
	})

	if len(this.addr) == 0 {
		golog.Warnf("[%s]缺少addr配置, 无法启动服务", this.caption)
	}

	{
		_, port := utils.Split2Str(this.addr, ":")
		utils.RegisterFactoryVar(fmt.Sprintf("%s-addr-port", this.PlugId), port)
		utils.RegisterFactoryVar("localhttp", fmt.Sprintf("http://127.0.0.1:%s", port))
		go this.startHttpService()
		time.Sleep(time.Second * 1)
	}

	return nil
}

func (this *NetHttpPlgCtrl) OnHttpCmd(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()

	rval, errcode, err := plgbase.ReloadPlgFromJSON(rec.Req)
	if err != nil {
		rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
	} else {
		if rval == nil {
			rec.ResponseJSONValues("code", 0)
		} else if json, ok := rval.(*wrapper.SuperValue); ok {
			PrjHttpUtils.ResponseJSON2(w, json)
		}
	}
}

func (this *NetHttpPlgCtrl) OnHttpClosePlugin(w http.ResponseWriter, r *http.Request) {
	defer utils.PanicHandler()
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()

	id := rec.QryAsString("id", "")
	if len(id) == 0 {
		rec.ResponseJSONValues("id", id, "code", -1, "msg", "invalid params")
	} else {
		statusCode := plgbase.PluginTryClose(id, int8(rec.QryAsInt("changedflag", 1)), int8(rec.QryAsInt("flag", 0)))
		rec.ResponseJSONValues("id", id, "code", statusCode)
	}
}

func (this *NetHttpPlgCtrl) OnHttpReload(w http.ResponseWriter, r *http.Request) {
	defer utils.PanicHandler()
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()

	utils.DefaultSubscribeCenter().DispatchData("/plug/reload", this, "reload", nil)
	time.Sleep(time.Millisecond)
	rec.ResponseJSONValues("code", 0)

}

func doPlugHttpIndex(plug interface{}, rec *PlgHttpRec) bool {
	if intf, ok := plug.(IHttpIndex); ok {
		rval, errcode, err := intf.OnHttpIndex(rec)
		if err != nil {
			rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
		} else {
			if json, ok := rval.(*wrapper.SuperValue); ok {
				PrjHttpUtils.ResponseJSON2(rec.RespW, json)
			}
		}
		return true
	} else {
		return false
	}
}

func doPlugHttpDo(plug interface{}, rec *PlgHttpRec) bool {
	if intf, ok := plug.(IHttpDo); ok {
		rval, errcode, err := intf.OnHttpDo(rec)
		if err != nil {
			rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
		} else {
			if json, ok := rval.(*wrapper.SuperValue); ok {
				PrjHttpUtils.ResponseJSON2(rec.RespW, json)
			}
		}
		return true
	} else {
		return false
	}
}

func doPlugHttpReload(plug interface{}, rec *PlgHttpRec) bool {
	if intf, ok := plug.(IHttpReload); ok {
		rval, errcode, err := intf.OnHttpReload(rec)
		if err != nil {
			rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
		} else {
			if json, ok := rval.(*wrapper.SuperValue); ok {
				PrjHttpUtils.ResponseJSON2(rec.RespW, json)
			}
		}
		return true
	} else {
		return false
	}
}

func doPlugHttpStatus(plug interface{}, rec *PlgHttpRec) bool {
	if intf, ok := plug.(plgbase.IStatusRequest); ok {
		req := wrapper.NewSVObject()
		rec.RangeURLParam(func(k, v string) bool {
			req.SetKeyValues(k, v)
			return true
		})
		resp := intf.DoStatusRequestRequest(req)
		if err, ok := resp.(error); ok {
			http.Error(rec.RespW, err.Error(), 503)
		} else if json, ok := resp.(*wrapper.SuperValue); ok {
			if json == nil {
				json = wrapper.NewSVObject()
			}
			if json.UseCache() {
				defer json.Clear()
			}
			if rec.QryAsInt("json", 0) == 1 {
				key := rec.QryAsString("jsonkey", "")
				if len(key) > 0 {
					rec.RespW.Write([]byte(json.StringByPath(key, rec.QryAsString("defval", ""))))
				} else {
					rec.RespW.Write([]byte(json.AsJSONString(false)))
				}
			} else {
				if json.IsObject() {
					PrjHttpUtils.JSON2TableRec(rec.RespW, "", json.AsJSONString(false))
				} else {
					PrjHttpUtils.JSON2Table(rec.RespW, "", json.AsJSONString(false))
				}

			}
		} else if synclst, ok := resp.(*utils.SyncCycleList); ok {
			rec.ResponseSyncListNEx(synclst)
		} else if buf, ok := resp.([]byte); ok {
			rec.RespW.Write(buf)
		} else if str, ok := resp.(string); ok {
			rec.RespW.Write([]byte(str))
		} else {
			rec.RespW.Write([]byte("request done!"))
		}
		return true
	} else if intf, ok := plug.(IHttpStatus); ok {
		rval, errcode, err := intf.OnHttpStatus(rec)
		if err != nil {
			rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
		} else {
			if json, ok := rval.(*wrapper.SuperValue); ok {
				PrjHttpUtils.ResponseJSON2(rec.RespW, json)
			}
		}
		return true
	} else {
		return false
	}

}

func (this *NetHttpPlgCtrl) OnHttPlugService(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()
	defer rec.Close()
	if this.http_allow_cross_access {
		PrjHttpUtils.AllowCrossAccessSet(rec.RespW)
	}
	uri := r.URL.Path
	uri = strings.TrimPrefix(uri, "/plug/")
	if len(uri) == 0 {
		http.Error(w, fmt.Sprintf("invalid url(%s)!", uri), 503)
		return
	}
	plugid, path := utils.CutPrefixByRune(uri, 0, 0, '/')
	if len(plugid) == 0 {
		//http.Error(w, fmt.Sprintf("invalid url(%s)!", uri), 503)
		plugid = path
		path = ""
	}

	obj := plgbase.FindPluginByID(plugid)

	if obj == nil {
		http.Error(w, fmt.Sprintf("plug:%s not found!", plugid), 404)
	} else {
		rec.PlugPath = path
		if len(path) <= 1 { // "/", ""
			if !doPlugHttpIndex(obj, rec) {
				http.Error(w, fmt.Sprintf("plug:%s not support IHttpIndex", plugid), 503)
			}
			return
		}

		remainPath := strings.TrimPrefix(path, "/")

		/**
		  "do"
		  "do/xxx
		  "reload"
		*/
		action, remainPath1 := utils.CutPrefixByRune(remainPath, 0, 0, '/')
		if len(action) == 0 {
			action = remainPath1
			remainPath1 = ""
		}
		rec.UriAction = action
		actionlstr := strings.ToLower(action)
		if strings.Compare(actionlstr, "do") == 0 {
			if doPlugHttpDo(obj, rec) {
				return
			}
		} else if strings.Compare(actionlstr, "status") == 0 {
			if doPlugHttpStatus(obj, rec) {
				return
			}
		} else if strings.Compare(actionlstr, "reload") == 0 {
			if doPlugHttpReload(obj, rec) {
				return
			}
		}

		if !doPlugHttpIndex(obj, rec) {
			http.Error(w, fmt.Sprintf("plug:%s not support IHttpIndex", plugid), 503)
		}
		return
	}
}

func (this *NetHttpPlgCtrl) OnHttPlugStatusList(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()
	defer rec.Close()
	itype := rec.QryAsInt("type", 0)
	if itype == 99 {
		var sb utils.BytesBuilder

		json := wrapper.NewSVObject()
		runtime.ReadMemStats(&this.memStats)
		sb.Appendf("内存占用:%s, 存活对象:%d, HeapAlloc:%s, HeapObjects:%d\r\n",
			utils.HumanFilesizeU(this.memStats.Sys), this.memStats.Mallocs-this.memStats.Frees, utils.HumanFilesizeU(this.memStats.HeapAlloc), this.memStats.HeapObjects)
		json.SetKeyValues("runsecs", PrjHttpUtils.GetRunSecs(), "duration", utils.HumanTimeStr(PrjHttpUtils.GetRunSecs()),
			"buildtime", plgbase.BUILD_TIME, "uuid", PrjConf.APP_UUID,
			"debugmode", utils.DEBUG_MODE,
			"addr", r.RemoteAddr, "timeunixsecs", time.Now().Unix(),
			"meminfo", sb.String(),
			"starttime", utils.DateTimeString3(this.startT),
			"workdir", utils.GetCurrentWorkingPath(),
			"approot", utils.GetExecuteFileDirectory(),
			"http-allow-cross-access", this.http_allow_cross_access,
			"time", utils.DateTimeString3(time.Now()))
		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, true))
		} else {
			PrjHttpUtils.JSON2TableRec(rec.RespW, "", json.AsJSONString(false))
		}
	} else if itype == 98 {
		//rec.ResponseSyncListNEx(this.msglst)
	} else if itype == 1 {
		json := wrapper.NewSVArray()
		searchval := rec.QryAsString("searchval", "")
		sn := 0
		maxn := rec.QryAsInt("n", 100)
		this.reqlst.ListRangeV2(func(key, val interface{}) bool {
			pojo := val.(*PlgCtrlHttpRequestRec)
			ok := true
			if len(searchval) > 0 {
				ok = strings.Contains(pojo.Uri, searchval)
			}
			return ok
		}, func(k0, v0, k1, v1 interface{}) bool {
			p0 := v0.(*PlgCtrlHttpRequestRec)
			p1 := v1.(*PlgCtrlHttpRequestRec)
			return p0.ReqMonitor.CurrN > p1.ReqMonitor.CurrN
		}, func(key, val interface{}) bool {
			sn++
			pojo := val.(*PlgCtrlHttpRequestRec)
			itm := json.AppendObject()
			itm.SetKeyValue("sn", sn)
			itm.SetKeyValue("id", pojo.Id)
			itm.SetKeyValue("status", pojo.ReqMonitor.Info())
			itm.SetKeyValue("last", utils.DateTimeString2(pojo.LastReqT))
			itm.SetKeyValue("head", pojo.ReqHead)
			itm.SetKeyValue("from", pojo.FromAddr)
			return sn < maxn
		})

		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, false))
		} else {
			PrjHttpUtils.JSON2Table(rec.RespW, "", json.AsJSONString(false))
		}
	} else {
		var sb utils.BytesBuilder
		PrjHttpUtils.AppendIndexBody(&sb, "status", "statuslist?type=99")
		//PrjHttpUtils.AppendIndexBody(&sb, "msglist", "status?type=98&n=100&searchval=")
		PrjHttpUtils.AppendIndexBody(&sb, "lst", "statuslist?type=1&n=100&searchval=")
		PrjHttpUtils.AppendIndexBody(&sb, "workers", "status/workers?id=&detail=0")
		PrjHttpUtils.AppendIndexBody(&sb, "gobaseworkers", "status/gobase/workers?id=&detail=0")
		PrjHttpUtils.AppendIndexBody(&sb, "gobasechantask", "status/gobase/chantask?id=&all=0")
		PrjHttpUtils.AppendIndexBody(&sb, "changed", "changed")
		PrjHttpUtils.AppendIndexBody(&sb, "plugclose", "close?changedflag=1&flag=0&id=")
		PrjHttpUtils.ResponseBodyHtml(rec.RespW, sb.String())
	}
	return
}

func (this *NetHttpPlgCtrl) OnHttPlugStatus(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()
	defer rec.Close()
	plugid := rec.QryAsString("id", "")
	if len(plugid) == 0 {
		var sb utils.BytesBuilder
		runtime.ReadMemStats(&this.memStats)
		sb.Appendf("内存占用:%s, 存活对象:%d, HeapAlloc:%s, HeapObjects:%d\r\n",
			utils.HumanFilesizeU(this.memStats.Sys), this.memStats.Mallocs-this.memStats.Frees, utils.HumanFilesizeU(this.memStats.HeapAlloc), this.memStats.HeapObjects)
		json := wrapper.NewSVObject()
		json.SetKeyValues("runsecs", PrjHttpUtils.GetRunSecs(), "duration", utils.HumanTimeStr(PrjHttpUtils.GetRunSecs()),
			"buildtime", plgbase.BUILD_TIME, "uuid", PrjConf.APP_UUID,
			"debugmode", utils.DEBUG_MODE,
			"machine-uuid", fmt.Sprintf("%s", utils.MD5Buf([]byte(dnet.GetFirstMacAsStringEx()))),
			"rate-limit-block-n", this.blockN,
			"rate-limit-n", this.ratelimitN,
			"rate-limit-n-per", this.ratelimitNPer,
			"goSDKVer", runtime.Version(),

			"plg-alive-n", plgbase.GetPlgAliveObjectCnt(),
			"supervalue-status", wrapper.GetSuperValueStatus(),
			"addr", r.RemoteAddr, "timeunixsecs", time.Now().Unix(),
			"meminfo", sb.String(),
			"starttime", utils.DateTimeString3(this.startT),
			"workdir", utils.GetCurrentWorkingPath(),
			"approot", utils.GetExecuteFileDirectory(),
			"time", utils.DateTimeString3(time.Now()))
		//var memstat runtime.MemStats
		//runtime.ReadMemStats(&memstat)
		//json.SetKeyValue("mem-app", utils.HumanFilesizeU(memstat.Sys))   // 服务现在使用的内存
		//json.SetKeyValue("mem-GCSys", utils.HumanFilesizeU(memstat.GCSys))   //
		//json.SetKeyValue("mem-raw", fmt.Sprintf("%+v", memstat))
		w.Write(json.AsJSONBytes(false))
		return
	}
	obj := plgbase.FindPluginByID(plugid)
	if obj == nil {
		http.Error(w, fmt.Sprintf("plug:%s not found!", plugid), 503)
	} else {
		if intf, ok := obj.(plgbase.IStatusRequest); ok {
			req := wrapper.NewSVObject()
			rec.RangeURLParam(func(k, v string) bool {
				req.SetKeyValues(k, v)
				return true
			})
			resp := intf.DoStatusRequestRequest(req)
			if err, ok := resp.(error); ok {
				http.Error(w, err.Error(), 503)
			} else if json, ok := resp.(*wrapper.SuperValue); ok {
				PrjHttpUtils.ResponseJSON2(w, json)
			} else if buf, ok := resp.([]byte); ok {
				w.Write(buf)
			} else if str, ok := resp.(string); ok {
				w.Write([]byte(str))
			} else {
				w.Write([]byte("request done!"))
			}
		} else if intf, ok := obj.(IHttpStatus); ok {
			rval, errcode, err := intf.OnHttpStatus(rec)
			if err != nil {
				rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
			} else {
				if json, ok := rval.(*wrapper.SuperValue); ok {
					PrjHttpUtils.ResponseJSON2(w, json)
				}
			}
		} else {
			http.Error(w, fmt.Sprintf("plug:%s not support IHttpStatus", plugid), 503)
		}
	}
}

func (this *NetHttpPlgCtrl) OnHttPlugChanged(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()
	defer rec.Close()
	searchval := rec.QryAsString("searchval", "")
	confrow := wrapper.NewSVObject()
	confrow.SetKeyValues("sn", "sn", "id", "id", "type", "type", "err", "err", "t", "time", "conf", "conf")
	json := wrapper.NewSVArray()

	plgbase.GetPlgChangedData(json, searchval)

	strTitle := confrow.AsJSONString(true)

	css := wrapper.NewSVArray()
	{
		c1 := css.AppendObject()
		c1.SetKeyValues("id", "id", "width", "150px")
	}
	{
		c1 := css.AppendObject()
		c1.SetKeyValues("id", "t", "width", "180px")
	}
	{
		c1 := css.AppendObject()
		c1.SetKeyValues("id", "err", "width", "180px")
	}
	{
		c1 := css.AppendObject()
		c1.SetKeyValues("id", "conf", "width", "500px")
	}
	if rec.QryAsInt("json", 0) == 1 {
		rec.ResponseText(json.AsJSONStringEx(false, true))
	} else {
		PrjHttpUtils.JSON2TableEx(rec.RespW, strTitle, json.AsJSONString(false), css)
	}

}

func (this *NetHttpPlgCtrl) OnHttPlugDo(w http.ResponseWriter, r *http.Request) {
	rec := NewPlgHttpRec(w, r)
	rec.CheckDecodeBody()
	defer rec.Close()
	plugid := rec.QryAsString("id", "")
	obj := plgbase.FindPluginByID(plugid)
	if obj == nil {
		http.Error(w, fmt.Sprintf("plug:%s not found!", plugid), 503)
	} else {
		if intf, ok := obj.(IHttpDo); ok {
			rval, errcode, err := intf.OnHttpDo(rec)
			if err != nil {
				rec.ResponseJSONValues("code", -1, "errcode", errcode, "msg", err.Error())
			} else {
				if json, ok := rval.(*wrapper.SuperValue); ok {
					PrjHttpUtils.ResponseJSON2(w, json)
				}
			}
		} else {
			http.Error(w, fmt.Sprintf("plug:%s not support IHttpDo", plugid), 503)
		}
	}
}

type HandlerWrapper struct {
	fn http.HandlerFunc
}

func (this HandlerWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	this.fn(w, r)
}
